﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Bson;
using JXT.PrimaryKey.Batman.Collections;
using JXT.PrimaryKey.Batman.Reflection;
using JXT.PrimaryKey.Batman.Core.Configuration;
using JXT.PrimaryKey.Batman.Domain.Models;
using JXT.PrimaryKey.Batman.Domain.Specifications;
using JXT.PrimaryKey.Batman.Domain.Specifications.Expressions;

namespace JXT.PrimaryKey.Batman.Domain.Repositories.Mongo
{
    public abstract class MongoRepositoryBase<TEntity, TKey> : IRepository<TEntity>,
        IReadRepository<TEntity, TKey>,
        IWriteRepository<TEntity, TKey>,
        ISpecificationRepository<TEntity>
        where TEntity : IEntity, new()
    {
        protected static readonly SafeMode _defaultSafeMode = SafeMode.True;

        private static readonly string _defaultTableName = typeof(TEntity).Name.ToLower();

        private static bool? _isCollectionExists = null;

        private MongoCollection _mongoQuery;

        protected MongoRepositoryContext Context { get; private set; }

        public MongoRepositoryBase(MongoRepositoryContext context)
        {
            Context = context;
        }

        RepositoryContext IRepository<TEntity>.Context
        {
            get { return Context; }
        }

        protected ServiceConfigurationSection ServiceConfigration
        {
            get
            {
                return SupernovaConfigurationManager.GetServiceConfigurationSection();
            }
        }

        protected virtual TRepository LoadRepository<TRepository>()
        {
            return JXT.PrimaryKey.Batman.Container.ContainerManager.Repository.Resolve<TRepository>();
        }

        protected bool IsCollectionExists
        {
            get
            {
                if (_isCollectionExists == null || _isCollectionExists == false)
                {
                    _isCollectionExists = MongoQuery.Exists();
                }
                return _isCollectionExists.Value;
            }
        }
        
        protected virtual MongoCollection MongoQuery
        {
            get
            {
                if (_mongoQuery == null)
                {
                    _mongoQuery = Context.Database.GetCollection(_defaultTableName, _defaultSafeMode);
                }
                return _mongoQuery;
            }
        }

        public virtual bool Exists(TKey key)
        {
            return MongoQuery.Count(Query.EQ("Id", BsonValue.Create(key))) > 0;
        }

        public virtual TEntity GetSingle(TKey key)
        {
            //var document = MongoQuery.FindOneAs<BsonDocument>(Query.EQ("id", BsonValue.Create(key)));
            //return TryReadEntity(document);
            return MongoQuery.FindOneAs<TEntity>(Query.EQ("Id", BsonValue.Create(key)));
        }

        public virtual IEnumerable<TEntity> GetList()
        {
            //var documentList = MongoQuery.FindAllAs<BsonDocument>();
            //IList<TEntity> list = new List<TEntity>();
            //foreach (var item in documentList)
            //{
            //    list.Add(TryReadEntity(item));
            //}
            //return list;
            return MongoQuery.FindAllAs<TEntity>().ToList();
        }

        public virtual void Add(TEntity entity)
        {
            //BsonDocument document = new BsonDocument();
            //WriteToDocument(entity, document);
            //MongoQuery.Insert(document);
            MongoQuery.Insert<TEntity>(entity, _defaultSafeMode);
        }

        public virtual void Update(TEntity entity)
        {
            //var idProperty = entity.GetMetadataCacheItem("id");
            //if (idProperty != null)
            //{
            //    var idValue = idProperty.Property.Get(entity);
            //    var document = MongoQuery.FindOneAs<BsonDocument>(Query.EQ("id", BsonValue.Create(idValue)));
            //    if (document != null)
            //    {
            //        WriteToDocument(entity, document);
            //        MongoQuery.Save(document);
            //    }
            //}
            MongoQuery.Save<TEntity>(entity, _defaultSafeMode);
        }

        public virtual void Delete(TKey key)
        {
            MongoQuery.Remove(Query.EQ("Id", BsonValue.Create(key)));
        }

        public virtual int Count(ISpecification<TEntity> specification)
        {
            throw new NotSupportedException();
        }

        public virtual TEntity GetSingle(ISpecification<TEntity> specification)
        {
            throw new NotSupportedException();
        }

        public virtual IPagedCollection<TEntity> GetList(IPagedSpecification<TEntity> specification)
        {
            throw new NotSupportedException();
        }

        protected virtual T TryReadFromDocument<T>(BsonDocument document) where T: new()
        {
            var array = typeof(T).GetMetadataCacheItems();
            T result = new T();
            foreach (var item in array)
            {
                BsonValue value;
                if (document.TryGetValue(item.Name.ToLower(), out value))
                {
                    item.Property.Set(result, Convert.ChangeType(value, item.Property.Property.PropertyType));
                }
            }
            return result;
        }

        protected virtual TEntity TryReadEntity(BsonDocument document)
        {
            return TryReadFromDocument<TEntity>(document);
        }

        protected virtual void WriteToDocument(TEntity entity, BsonDocument document)
        {
            var array = entity.GetMetadataCacheItems();
            foreach (var item in array)
            {
                var value = item.Property.Get(entity);
                document.Set(item.Name.ToLower(), BsonValue.Create(value));
            }
        }

        protected IMongoUpdate BuildUpdateQuery<T>(T entity, params string[] excludes) where T : IEntity
        {
            if (entity == null)
                throw new ArgumentNullException("entity");
            var properties = entity.GetMetadataCacheItems();
            string idString = entity.GetIdentityName();
            Func<string, bool> isInExcludesFunction = name =>
            {
                if (excludes == null)
                    return false;
                return excludes.Count(e => e.Equals(name, StringComparison.InvariantCultureIgnoreCase)) > 0;
            };
            UpdateBuilder update = null;
            foreach (var item in properties)
            {
                if (item.Name != idString && !isInExcludesFunction(item.Name))
                {
                    if (update == null)
                        update = MongoDB.Driver.Builders.Update.Set(item.Name, BsonValue.Create(item.Property.Get(entity)));
                    else
                        update = update.Set(item.Name, BsonValue.Create(item.Property.Get(entity)));
                }
            }
            return update;
        }
    }
}
